// 
// Copyright (c) 2006-2008 Ben Motmans
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Author(s):
//    Ben Motmans <ben.motmans@gmail.com>
//

using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;

namespace Anculus.Core
{
	/// <summary>
	/// 
	/// </summary>
	public class TimedDispatcher : AbstractDispatcher
	{
		protected bool keepAlive;
		protected Thread thread;
		
		protected Stopwatch stopwatch;

		public TimedDispatcher ()
			: this (false, false, ThreadPriority.AboveNormal)
		{
		}

		public TimedDispatcher (bool keepAlive)
			: this (keepAlive, false, ThreadPriority.AboveNormal)
		{
		}
		
		public TimedDispatcher (bool keepAlive, bool isBackground, ThreadPriority priority)
		{
			this.keepAlive = keepAlive;
			IsBackground = isBackground;
			ThreadPriority = priority;
		}

		public bool IsBackground { get; protected set; }
		
		public ThreadPriority ThreadPriority { get; protected set; }

		public bool KeepAlive
		{
			get { return keepAlive; }
			set {
				keepAlive = value;
				
				if (Monitor.TryEnter (this, LockTimeout)) {
					//if keepAlive is set to false, then the thread must be awaken
					try {
						if (thread != null && thread.IsAlive && !keepAlive)
							thread.Interrupt ();
					} catch (ThreadInterruptedException) {
					} finally {
						Monitor.Exit (this);
					}
				}
			}
		}
		
		public override void Initialize ()
		{
			base.Initialize ();
			
			threadId = -1;
			stopwatch = new Stopwatch ();
		}
		
		public override void Shutdown ()
		{
			if (Monitor.TryEnter (this, LockTimeout)) {
				try {
					keepAlive = false;
					
					thread.Join (1000);
					if (thread != null && thread.IsAlive) {
						try {
							thread.Abort ();
						} catch (ThreadAbortException) {}
					}
					
					thread = null;
				} finally {
					Monitor.Exit (this);
				}
			}
			
			base.Shutdown ();
		}

		protected override void WakeupProcessQueue ()
		{
			Log.Trace ();
			
			if (thread != null && thread.IsAlive) {
				try {
					Log.Debug ("Interrupt");
					thread.Interrupt ();
				} catch (ThreadInterruptedException) {}
			} else {
				Log.Debug ("TimedDispatcher.ExecuteProcessQueue: THREAD START");
				
				thread = new Thread (ExecuteBackgroundThread);
				thread.IsBackground = IsBackground;
				thread.Priority = ThreadPriority;
				
				thread.Start ();
			}
		}

		protected int CalculateNextDelay ()
		{
			if (Monitor.TryEnter (this, LockTimeout)) {
				try {
					if (queue.Count == 0)
						return -1;
					
					int delay = int.MaxValue;
					foreach (IDispatchContainer container in queue.Values) {
						if (container.RemainingDelay < delay)
							delay = container.RemainingDelay;
					}
					
					return delay;
				} finally {
					Monitor.Exit (this);
				}
			} else {
				return 100; //try again in 100 ms
			}
		}
		
		protected void ExecuteBackgroundThread ()
		{
			Log.Trace ();

			thread = Thread.CurrentThread;
			threadId = thread.ManagedThreadId;
			
			while (keepAlive || queue.Count > 0) {
				int wait = CalculateNextDelay ();
				while (wait >= 0) {
					Log.Debug ("TimedDispatcher.ExecuteProcessQueue: wait {0}", wait);

					//measure the real sleep time, since the thread is interrupted
					//every time QueueContainer is called
					stopwatch.Reset ();
					stopwatch.Start ();

					try {
						Thread.Sleep (wait);						
					} catch (ThreadInterruptedException) {}
					
					stopwatch.Stop ();
					
					if (stopwatch.ElapsedMilliseconds > (long)int.MaxValue) //you never know
						ProcessQueue (int.MaxValue);
					else
						ProcessQueue ((int)stopwatch.ElapsedMilliseconds);
					
					wait = CalculateNextDelay ();
				}
				
				Log.Debug ("Sleep INFINITE");
				try {
					Thread.Sleep (Timeout.Infinite);						
				} catch (ThreadInterruptedException) {}
			}

			Log.Debug ("TimedDispatcher.ExecuteProcessQueue: THREAD STOP");
		}
	}
}
